iT邦幫忙

2026 iThome 鐵人賽

DAY 8
0
佛心分享-IT 人職涯歷練

從 UI/UX 麻瓜到工程魔法師|從讀懂程式開始系列 第 8

Day 08|useEffect 到底什麼時候執行?為什麼一進頁面 API 就自己打了?

  • 分享至 

  • xImage
  •  

開始看 React 專案後,我很常遇到這種程式:

useEffect(() => {
  fetchData();
}, []);

第一次看到時,我會直接把它翻成:

「它在監聽什麼?」

因為以前學 JavaScript 時,看到:

addEventListener(...)

就是在監聽事件。

所以看到 useEffect,很自然也會往「監聽」的方向理解。

但後來才慢慢發現:

useEffect 並不是單純拿來監聽使用者事件的。

像按鈕點擊,React 本來就有:

onClick

輸入內容改變也有:

onChange

useEffect 處理的是另外一類事情。


先從最常看到的例子開始

例如:

useEffect(() => {
  fetchData();
}, []);

假設:

fetchData();

裡面會呼叫 API。

那整個流程可以先理解成:

Component 開始 Render
↓
React 把畫面更新完成
↓
useEffect 執行
↓
fetchData()
↓
呼叫 API
↓
取得資料
↓
更新 State
↓
再次 Render

所以你可能什麼都還沒按,

頁面一進來,Network 就已經出現 Request。

不是因為使用者觸發了按鈕。

而是這段:

useEffect(() => {
  fetchData();
}, []);

在 Component 顯示之後執行了。


useEffect 可以先理解成什麼?

我目前比較喜歡這樣理解:

當 React Render 完畫面後,如果還有一些事情需要跟 React 外部的東西同步,就可以使用 Effect。

例如:

呼叫 API
設定 Timer
訂閱事件
操作瀏覽器 API
跟第三方套件同步

這些事情不只是「計算 JSX 要長什麼樣子」。

它們會碰到 React Component 外面的世界。

所以 React 把這類事情稱為:

Side Effect

也就是「副作用」。


什麼是 Side Effect?

這個名字第一次看到很容易覺得很可怕。

其實先用一個簡單對比就好。

例如:

const fullName = firstName + lastName;

這只是根據現有資料算出另一個值。

沒有:

打 API
修改瀏覽器
設定 Timer
訂閱事件

這種事情通常不需要 useEffect

但:

fetch("/api/users");

會對外送出 Request。

這就是跟 Component 外面的系統互動。

因此:

useEffect(() => {
  fetch("/api/users");
}, []);

就比較符合 useEffect 的用途。


那後面的 [] 是什麼?

看到:

useEffect(() => {
  fetchData();
}, []);

最容易讓人困惑的就是最後:

[]

這個叫做:

dependency array

也就是「依賴陣列」。

它是在告訴 React:

這個 Effect 依賴哪些值。


空陣列 []

例如:

useEffect(() => {
  fetchData();
}, []);

可以先簡化理解成:

Component 第一次完成 Render 後,執行這個 Effect。

所以:

進入頁面
↓
Render
↓
useEffect
↓
fetchData()

之後即使 Component 因為其他 State 更新而重新 Render,這個 Effect 不會因那些值而再次執行。

這也是為什麼很多「頁面第一次載入資料」的程式會長這樣。


如果 dependency 裡有值呢?

例如:

useEffect(() => {
  fetchUser(userId);
}, [userId]);

這時候它依賴:

userId

流程可以想成:

第一次 Render
↓
執行 Effect
↓
fetchUser(userId)

之後 userId 改變
↓
重新 Render
↓
Effect 再執行
↓
用新的 userId 重新取得資料

所以:

[userId]

不是說:

「一直盯著 userId 看。」

而是:

這個 Effect 使用了 userId,而且當 userId 改變時,需要重新同步一次。


例如切換使用者

假設:

const [userId, setUserId] = useState(1);

Effect:

useEffect(() => {
  fetchUser(userId);
}, [userId]);

一開始:

userId = 1
↓
Render
↓
Effect
↓
GET /users/1

後來:

setUserId(2);

流程:

userId
1 → 2
↓
React Render
↓
React 發現 dependency 的 userId 改變
↓
Effect 再執行
↓
GET /users/2

這時候 useEffect 的存在就比較合理了。

因為:

外部 API 需要跟現在的 userId 保持同步。


那如果沒有 dependency array 呢?

還可能看到:

useEffect(() => {
  console.log("effect");
});

注意這裡沒有:

[]

這表示:

每次 Component Render 完之後,Effect 都會執行。

也就是:

Render
↓
Effect

State 改變
↓
Render
↓
Effect

又有 State 改變
↓
Render
↓
Effect

這時候就要特別小心。

如果 Effect 裡面又一直更新 State:

useEffect(() => {
  setCount(count + 1);
});

就可能形成:

Render
↓
Effect
↓
setCount()
↓
Render
↓
Effect
↓
setCount()
↓
...

最後一直重複。

這也是為什麼看到 useEffect 時,dependency 是很重要的線索。


所以 useEffect 不是「React 版 addEventListener」

這裡我以前很容易混在一起。

例如 JavaScript:

button.addEventListener("click", handleClick);

React 通常直接是:

<button onClick={handleClick}>
  Click
</button>

而不是:

useEffect(...)

所以:

使用者操作事件
→ onClick / onChange ...

Render 後需要同步外部系統
→ useEffect

這兩個概念要分開。


那為什麼專案裡常常「一進頁面就打 API」?

因為我們很常需要:

頁面顯示時,先取得資料。

例如:

const [users, setUsers] = useState([]);

useEffect(() => {
  fetch("/api/users")
    .then(res => res.json())
    .then(data => {
      setUsers(data);
    });
}, []);

流程:

第一次 Render
↓
目前 users = []
↓
畫面先 Render

Render 完成
↓
useEffect 執行
↓
Request API
↓
Response 回來
↓
setUsers(data)
↓
State 改變
↓
第二次 Render
↓
畫面出現 users

所以頁面載入時,不是:

API 完全跑完
↓
才開始 Render

而比較像:

先 Render
↓
Effect 發 Request
↓
資料回來
↓
更新 State
↓
再 Render

這也是理解 React 頁面資料流時很重要的一條線。


實際專案裡,我會怎麼讀?

例如看到:

useEffect(() => {
  fetchRoot();
}, [showFilter]);

以前我的第一反應可能是:

useEffect 到底是什麼?

現在我比較會先問:

這個 Effect 做什麼?
→ fetchRoot()

它依賴什麼?
→ showFilter

所以什麼情況會重新執行?
→ showFilter 改變

直接先得到:

第一次 Render
↓
fetchRoot()

showFilter 改變
↓
重新 Render
↓
fetchRoot() 再跑

再去看:

fetchRoot()

到底打哪支 API、資料回來放哪裡。

這樣就不用一開始被整段程式嚇到。


看到 useEffect,我現在會問三件事

比起背語法,我現在會先找:

① Effect 裡到底做了什麼?

② dependency array 裡有哪些值?

③ Effect 執行後,會不會更新 State?

因為這三件事通常就能幫助我畫出流程:

什麼條件
↓
觸發 Effect
↓
做什麼事情
↓
有沒有更新資料
↓
會不會再次 Render

但 API 不一定都要自己塞進 useEffect

實際專案裡,也很常使用其他封裝好的資料請求工具。

例如:

useRequest(...)

或其他 Data Fetching Library。

所以看到:

頁面載入
↓
打 API

不代表一定會找到:

useEffect

有些工具本身就幫忙處理了:

何時執行
loading
error
response

但不管使用什麼工具,我現在都會追同一件事:

這支 Request 到底是什麼時候被觸發的?


今天真正想記的是這張圖

React Render
↓
畫面完成

如果還需要跟外部世界同步
↓
useEffect

例如:
├─ API
├─ Timer
├─ Subscription
└─ Browser API

然後 dependency:

[]
→ 第一次完成 Render 後執行

[userId]
→ 初次執行
→ userId 改變後再執行

沒有 dependency array
→ 每次 Render 後都會執行

所以以後看到:

useEffect(() => {
  ...
}, [...]);

我不會再只問:

「這段語法是什麼?」

而是會先問:

它在跟什麼東西同步?又是什麼變化讓它需要重新同步?

下一篇就可以正式進入我在真實專案裡更常遇到的資料請求方式:

useRequest 到底在幹嘛?API 是誰打出去的,Response 又去了哪裡?


上一篇
Day 07|Props 傳到懷疑人生時,Context 到底是在解決什麼?
下一篇
Day 09|useRequest 到底在幹嘛?先追 API 從哪裡出去、資料又跑去哪裡
系列文
從 UI/UX 麻瓜到工程魔法師|從讀懂程式開始9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言